메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

Enterprise Flex RIA 해부(9) : 명사와 엔티티 빈즈

한빛미디어

|

2008-08-22

|

by HANBIT

12,145

제공 : 한빛 네트워크
저자 : Tony Hillerson
역자 : 임성진
원문 : Anatomy of an Enterprise Flex RIA Part 9: Nouns and Entity Beans

지난 기사에서는 개발 주기와 지속성 레이어(persistence layer)에 대한 의존성을 살펴보았다. 이제 지속성 레이어를 구성하는 실제 액션스크립트와 자바 코드를 살펴볼 것이다. 모델링해야 데이터를 산출하여 데이터베이스에 영속화하는 가장 간단한 방법은 유스케이스를 살펴보고 명사를 골라내는 것이다. 명사는 “이 책에는 일정한 제목이 있다”라는 것처럼 우리가 어떤 것에 관해 이야기할 수 있는 무언가를 뜻한다. 동사는 사용자가 할 수 있게 허용해야 할 행동을 말한다. 명사는 데이터베이스 테이블과 그와 같은 테이블에 매핑되는 클래스가 되며, 동사는 이러한 클래스의 인스턴스를 조작하고 인스턴스에 작용하는 코드가 된다. (형용사와 부사는 지금 다루지 않겠다.)

유스케이스를 잠깐 살펴본 결과 아래의 5개의 명사가 있다는 사실이 드러났다:
  • Books
  • Subjects
  • Authors
  • Users
  • Reservations
여기서는 저자(authors)와 사용자(users)를 대략 같은 것으로 보고 둘을 묶어서 사람(people)으로 부르기로 하자. 그러면 아래의 명사들이 남는다.
  • Books
  • Subjects
  • People
  • Reservations
아래 [그림 10]에서 이 명사들에 기초한 데이터베이스 스키마를 볼 수 있다.


[그림 10] Books, Subjects, People, Reservation 명사에 기초한 데이터베이스 스키마

이제 이 테이블에 영속화하는 자바 클래스를 만들어 보자. 나는 데이터베이스 테이블은 복수 명사형으로 쓰고, 해당 테이블에 대응되는 객체는 단수로 쓰는 루비 온 레일즈의 명명 관례(naming convention)를 따르길 좋아한다.

아래는 클래스를 테이블에 매핑하는 문법을 보여준다.
@Entity
@Table(name="books")
public class Book {
 ...
src 안에는 main이라 부르는 프로젝트의 주요 소스 코드를 저장하기 위한 디렉터리가 들어있다. 같은 레벨에는 test라 부르는 테스트 코드를 저장하기 위한 디렉터리도 있다. 코드는 main 디렉터리와 test 디렉터리로 들어가며 코드 작성에 사용된 언어에 따라 구성된다. 이 경우, java 디렉터리에는 데이터 프로젝트 디렉터리와 웹 프로젝트의 java 및 flex 디렉터리의 자바 코드가 들어간다.

이러한 타입의 클래스를 엔티티(Entity)라 하며, 엔티티는 기본적으로 명사와 같은 것으로 볼 수 있다(이름을 가진 어떤 것). 이것은 EJB 3.0으로부터 유래한 세 가지 빈 타입 중 하나이다. 우리는 세션 빈과 메시지 메시지 기반 빈(message-driven bean)과 같은 다른 타입의 빈에 대해서도 살펴볼 것이다. 한 가지 알아둘 점은 이 클래스는 아무것도 상속할 필요가 없다는 것인데, 이것은 이전 버전의 EJB와는 다른 점이다. 이 클래스에는 앳(@) 기호 다음에 오는 것을 처리하는 애노테이션은 두 가지가 있는데, @Table 애노테이션은 EJB에 이 클래스와 어떤 테이블이 매핑되는지 알려준다.

클래스와 테이블을 매핑하고 나면, 각 컬럼은 해당 컬럼에 대한 “접근자 메서드”와 대응될 필요가 있다. 자바 규약에는 이러한 메서드를 포함하는 클래스를 지칭하는 이름이 있는데, 이를 빈(bean)이라 한다. “빈 패턴(bean pattern)"을 따르는 클래스는 private 변수인 하나 이상의 ”프로퍼티(property)"와, 클래스에서 해당 프로퍼티에 대한 접근을 보호할 수 있게 해주는 “접근자(getter)"와 ”설정자(setter)" 메서드를 갖는다. 예를 들면, 아래는 Book 클래스의 코드를 나타낸 것이다:
package lcds.examples.bookie.entity;
...
@Entity
@Table(name="books")
public class Book {
 
     private String title;
     
 ...
     @Column(name="title")
     public String getTitle() {
           return title;
      }
     
     public void setTitle(String title) {
           this.title = title;
      }
 ...
Book 클래스는 private 프로퍼티인 title과 getTitle과 setTitle이라는 두 개의 public 메서드를 갖고 있다. 이것은 데이터와 그 데이터를 획득하기 위한 메서드로 구성되는 객체지향 프로그래밍의 객체에 대한 개념을 보여주며, 이를 통해 객체는 외부 접근으로부터 데이터를 보호할 수 있다.

이 클래스에는 @ 기호가 하나 더 있는데, @Column 애노테이션은 getTitle과 setTitle 메서드가 “Title"이라는 컬럼에 해당되는 프로퍼티에 접근하기 위한 접근자 메서드임을 EJB에 알려준다.

한 가지 더 밟아야 할 단계는 엔티티에서 공통 부분을 일반화하여 BaseEntity 클래스로 만드는 것이다. 여기에서는 EJB 리스너가 각 레코드에 생성 및 갱신된 타임스탬프를 삽입하여 변경에 좀 더 기민하게 대처할 수 있다. 각 테이블에는 “created_on"과 "updated_on" 컬럼이 포함되어야 한다. 이제 BaseEntity에 EntityListener 애노테이션을 설정함으로써 여러분은 EJB의 생명주기 이벤트가 발생할 때마다 호출된 객체의 인스턴스를 획득할 것이다.
package lcds.examples.bookie.entity.listeners;
...
@MappedSuperclass
@EntityListeners({CreateUpdateTimestampEventListener.class})
public abstract class BaseEntity implements Serializable {
     
     private Integer id;
     private Calendar createdOn;
     private Calendar updatedOn;
 
     @Id
     @GeneratedValue(strategy=GenerationType.AUTO)
     @Column(name="id")
     public Integer getId() {
           return id;
      }
 
     public void setId(Integer id) {
           this.id = id;
      }
     
     @Temporal(TemporalType.TIMESTAMP)
     @Column(name="created_on")
     public Calendar getCreatedOn() {
           return createdOn;
      }
 
     public void setCreatedOn(Calendar createdOn) {
           this.createdOn = createdOn;
      }
 
     @Temporal(TemporalType.TIMESTAMP)
     @Column(name="updated_on")
     public Calendar getUpdatedOn() {
           return updatedOn;
      }
 
     public void setUpdatedOn(Calendar updatedOn) {
           this.updatedOn = updatedOn;
      }
 ...
이러한 BaseEntity를 만들어 두면 id 프로퍼티와 같은 공통 코드를 넣어둘 수 있는 이점을 누릴 수 있다. BaseEntity를 확장하는 모든 클래스는 id를 얻기 위해 별도의 노력을 기울일 필요가 없다. 그리고 BaseEntity 클래스에 대한 테이블도 있어야 한다는 점을 잊어서는 안된다.

한 가지 알아둘 것은 BaseEntity 클래스에는 @Entity 대신 @MappedSuperclass 애노테이션이 설정되어 있다는 점인데, 이것은 BaseEntity는 데이터베이스에 바로 저장되지 않고 BaseEntity의 필드는 주로 매핑된 상위 클래스를 확장하는 다른 객체의 일부를 구성한다는 것을 뜻한다.

언급했던 바와 같이 우리는 타임스탬프 코드를 여기에도 둘 수 있다. 엔티티에 매핑한 각 테이블에는 "created_on"과 "updated_on" 컬럼이 있어야 한다. 엔티티가 BaseEntity를 확장할 경우 그와 같은 컬럼들은 자동적으로 값이 채워지는데, 이것은 상당히 유용한 기능이라 할 수 있다. 새로운 애노테이션인 @Temporal도 알아두자. 이 애노테이션은 EJB가 특정 날짜 타입의 프로퍼티에 들어있는 데이터와 데이터베이스의 날짜 타입의 컬럼과 매핑되게 한다.

자동화된 부분은 어떻게 작용하는가? 엔티티 리스너를 사용함으로써. 이번에는 리스너 배열을 담고 있는 @EntityListeners 애노테이션에 주목하자. 엔티티 리스너에는 엔티티의 생명주기, 가령 객체 생성이나 갱신, 또는 제거와 같은 각기 다른 시점에서 EJB가 자동적으로 호출하는 메서드가 포함되어 있다. [표 6]은 사용가능한 생명주기 이벤트에 대한 애노테이션을 나열하고, 해당 애노테이션을 설명한다.

life cycle event annotation DESCRIPTION
@PrePersist 엔티티가 데이터베이스에 기록되기 전에 호출되는 메서드
@PostPersist 엔티티가 데이터베이스에 기록된 후에 직접 호출되는 메서드
@PreUpdate 엔티티가 데이터베이스에 업데이트 되기 전에 호출되는 메서드
@PostUpdate 엔티티가 데이터베이스에 업데이트 된 후에 호출되는 메서드
@PreRemove 엔티티가 제거되기 전에 호출되는 메서드
@PostRemove 엔티티가 제거된 후에 호출되는 메서드
[표 6] 생명주기 이벤트 애노테이션

그리고 여기서 우리가 작성한 엔티티 리스너를 살펴보자:
public class CreateUpdateTimestampEventListener {
     
     @PrePersist
     public void setCreateDateTime(Object entity) 
               throws IllegalArgumentException {
           try {
                 BaseEntity stampable = (BaseEntity)entity;
                 stampable.setCreatedOn(new GregorianCalendar());
            } catch (Exception e) {
                 ...
            }
      }
     
     @PreUpdate
     public void setUpdateDateTime(Object entity)
               throws IllegalArgumentException {
           try {
                 BaseEntity stampable = (BaseEntity)entity;
                 stampable.setUpdatedOn(new GregorianCalendar());
            } catch (Exception e) {
                 ...
            }
      }
     
}
[그림 11]은 lcds.examples.bookie.entity 패키지에 들어있는 데이터 프로젝트의 매핑된 엔티티를 모두 보여준다.


[그림 11] 데이터 프로젝트에 맵핑된 엔티티

이제, ActionScript에서 엔티티를 모델링한 것을 살펴보자.

아래는 Book 엔티티에 대응되는 ActionScript 클래스를 나타낸 것이다:
package lcds.examples.bookie.entity {
     import lcds.examples.bookie.entity.BaseEntity;
     import lcds.examples.bookie.entity.Subject;
 
     [Managed]
     [RemoteClass(alias="lcds.examples.bookie.entity.Book")]
     public class Book extends BaseEntity {
  
           public var subject:Subject;
           public var title:String;
           public var isbn:String;
           public var author:Person;
  
      }
}
혼란을 줄이기 위해, ActionScript의 패키지가 자바 패키지와 어떻게 동일한지 주의하라. ActionScript Book 클래스도 BaseEntity를 확장하는데, 이 클래스에 대해서는 잠시 후에 알아보기로 하자. [RemoteClass] 메타데이터를 눈여겨 보라. [RemoteClass] 메타데이터의 alias 프로퍼티는 LCDS에 어느 자바 클래스가 이 클래스에 매핑되었는지 알려준다. 이것은 해당 문자열을 이 클래스에 매핑하는 컴파일러 지시자이다. 이렇게 하면 Flash Player의 LCDS 코드에서 클래스의 전체 경로를 포함한 이름(qualified name)을 가진 자바 클래스의 인스턴스를 획득할 때마다 이 클래스를 찾아 그 클래스의 인스턴스를 생성해야 함을 알게 된다. 지저분하고 처리하기 힘든 XML 인코딩 및 디코딩 작업을 할 필요가 없다!

[Managed] 메타 데이터 태그는 Flex에서 이 타입의 객체가 변경되는지를 지켜보게 하며, 설정에 따라 해당 객체의 변경 여부는 자동으로 LCDS로 통지된다.

마지막 당부 : Flex에는 자바의 “빈 패턴”과 같은 개념이 없으므로(그렇지만 더 나은 접근자/설정자 패턴을 갖고 있다), JavaBean에 매핑되는 ActionScript 빈에 포함된 프로퍼티는 public이어야 한다. 별것은 아니며, 이 객체에 담긴 데이터를 잘 다루기만 해도 별 문제없을 것이다.

ActionScript 엔티티는 bookie-ui 프로젝트의 lcds.examples.bookie.entity 패키지에 있다(그림 12 참조).


[그림 12] ActionScript 엔티티

엔티티는 Flex와 자바 측에서 모델로 만들어졌기 때문에 자바 측의 서비스를 호출할 수 있을 것이며, LCDS는 호출시 ActionScript 객체를 자바 객체로 변환할 수 있을 것이다. 자바는 그와 같은 작업을 해야 할 경우 그렇게 할 것이며, 필요하다면 그 객체들을 반대로 자바에서 ActionScript로 변환할 것이다. 이것은 LCDS가 제공하는 것 중 매우 기본적인 예일 뿐이다. 다음 섹션에서 LCDS의 기능을 더 살펴볼 것이다. 다음 연재 기사에서는 자바 관점에서의 서비스 계층에 관해 알아볼 것이다. 여기를 클릭하면 전체 시리즈(영문)를 볼 수 있다.
TAG :
댓글 입력
자료실

최근 본 책0